package org.molamil.acto.pages.masters {

	import org.molamil.acto.calls.Caller;
	import org.molamil.acto.core.loggers.Logger;
	import org.molamil.acto.pages.AbstractPageBlockContext;
	import org.molamil.acto.pages.AbstractPageBlockRequest;
	import org.molamil.acto.pages.InvalidContainerError;
	import org.molamil.acto.pages.PageContext;
	import org.molamil.acto.pages.PagesEvent;
	import org.molamil.acto.pages.PagesSession;
	import org.molamil.acto.states.AbstractIo3Manager;
	import org.molamil.acto.states.StateEvent;
	import org.molamil.acto.states.StateManager;
	import org.molamil.acto.utils.DisplayUtils;
	import org.molamil.acto.utils.EventUtils;
	import org.molamil.acto.utils.ObjectUtils;

	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.errors.IllegalOperationError;
	import flash.events.EventDispatcher;
	import flash.utils.getQualifiedClassName;

	/**
	 * XXX: Document.
	 * 
	 * @author Jorge Hernandez
	 */
	public class AbstractMaster extends EventDispatcher implements Master {

		
		// CONSTANTS
		
		private static const log:Logger = Logger.getLogger("AbstractMaster");
		
		protected static const ERROR_INVALID_TARGET:String = "ERROR: Invalid target \"{target}\" at page/block with id \"{id}\"";
		protected static const ERROR_INVALID_CONTAINER:String = "ERROR: Invalid container \"{container}\" at page/block with id \"{id}\"";
		protected static const INVALID_CONTAINER_INIT_METHOD:String = "init method \"{method}\" couldn't be called at \"{id}\" and will be " +
																	  "skipped since the method cannot define required parameters.";
		protected static const INVALID_CONTAINER_DESTROY_METHOD:String = "destroy method \"{method}\" couldn't be called at \"{id}\" and will be " +
																		 "skipped since the method cannot define required parameters.";
		
		
		// PROPERTIES
		
		protected var _isDisplayed:Boolean;
		protected var _isCleared:Boolean;
		protected var _context:AbstractPageBlockContext;
		protected var _request:AbstractPageBlockRequest;
		protected var _stateManager:StateManager;
		protected var _session:PagesSession;
		protected var _container:DisplayObjectContainer;
		protected var _target:Object;

		
		// CONSTRUCTOR
		
		public function AbstractMaster() {
			
			// Abstract check.
			if (getQualifiedClassName(this) == "org.molamil.acto.pages.masters::AbstractMaster")
            	throw new IllegalOperationError("AbstractMaster must be extended");
            	
			_isDisplayed = false;
			_isCleared = false;
						
		}
		
		
		// PUBLIC METHODS
		
		public function display():void {
			
			log.info("Displaying " + _context.id);
			log.info("Displaying with target: " + _context.target);
			
			// Dispatch a Display event before trying to display.
			var eventType:String = isPage() ? PagesEvent.PAGE_DISPLAY : PagesEvent.BLOCK_DISPLAY;
			dispatchEvent(new PagesEvent(eventType, false, false, null, _context, _request, _session));
			
			// The default container is the session root.
			_container = session.root;
			
			// Get the container from the context and check that it is a DisplayObjectContainer.
			if (context.container != null) {
				var containerDO:DisplayObject = DisplayUtils.findChild(session.root, context.container);
				if (!(containerDO is DisplayObjectContainer)) {
					var msg:String = ERROR_INVALID_CONTAINER.replace("{container}", context.container).replace("{id}", context.id);
					log.severe(msg);
					throw new InvalidContainerError(msg);
				}
				_container = DisplayObjectContainer(containerDO);
			}
			
			doDisplay();
			
		}
		
		public function clear():void {
			
			// Dispatch a Clear event before trying to clear.
			var eventType:String = isPage() ? PagesEvent.PAGE_CLEAR : PagesEvent.BLOCK_CLEAR;
			var pagesEvent:PagesEvent = new PagesEvent(eventType, false, false, null, _context, _request, _session, _target);
			dispatchEvent(pagesEvent);
			
			log.info("Clearing " + context.id);
			
			_stateManager.state = AbstractIo3Manager.STATE_OUT;
			
			doClear();
			
			_isCleared = true;
			
		}
		
		public function destroy():void {
			
			_isDisplayed = false;
			
			// Call the destroy methods of the target object configured in the session.
			if (_target != null) {
				for each (var methodName:String in session.destroyMethods) {
					log.info("Calling destroy method: " + methodName);
					log.info("Calling destroy method on displayObject: " + DisplayUtils.getPath(DisplayObject(_target)));
					if (ObjectUtils.containsEmptyMethod(_target, methodName)) {
						var f:Function = _target[methodName];
						f();
					} else if (ObjectUtils.containsMethod(_target, methodName)) {
						log.warning(INVALID_CONTAINER_DESTROY_METHOD.replace("{method}", methodName).replace("{id}", _context.id));
					} else {
						log.info("Destroy method not declared: " + methodName);
					}
				}
			}
			
			// Destroy the state manager.
			_stateManager.destroy();
			
			// Call to the subclass implemented specific destroy routines.
			doDestroy();
			
			// Clean up the target reference for garbage collection.
			log.info("Setting target to null");
			_target = null;
			
		}
		
		
		// PROTECTED METHODS

		protected function doDisplay():void {
			log.severe("doDisplay() must be overriden by the AbstractMaster subclass: " + getQualifiedClassName(this));
		}
		
		protected function doClear():void {
			//Empty;
		}
		
		protected function doDestroy():void {
			log.severe("doDestroy() must be overriden by the AbstractMaster subclass: " + getQualifiedClassName(this));
		}
		
		protected function init():void {
			
			_isDisplayed = true;
			
			// Merge context and request properties.
			ObjectUtils.merge(_target, _context.props);
			ObjectUtils.merge(_target, _request.params);
			
			// Pass a references to acto if declared to the target.
			if (session.actoRequestVarName) {
				var actoRequestVar:Object = new Object();
				actoRequestVar[session.actoRequestVarName] = request;
				ObjectUtils.merge(_target, actoRequestVar);
			}
			
			// Call the init methods configured in the session.
			for each (var methodName:String in session.initMethods) {
				log.info("Calling init method: " + methodName);
				log.info("Calling init method on displayObject: " + DisplayUtils.getPath(DisplayObject(_target)));
				if (ObjectUtils.containsEmptyMethod(_target, methodName)) {
					var f:Function = _target[methodName];
					f();
				} else if (ObjectUtils.containsMethod(_target, methodName)) {
					log.warning(INVALID_CONTAINER_INIT_METHOD.replace("{method}", methodName).replace("{id}", _context.id));
				} else {
					log.info("Init method not declared: " + methodName);
				}
			}
			
			// Perform other configured calls on target.
			Caller.run(_target, _request.calls);
			Caller.run(_target, _context.calls);
			
			// Set state to in and merge properties.
			_stateManager.target = target;
			if (_context != null && _context.props != null)
				ObjectUtils.merge(_stateManager, _context.props[_session.stateManagerPropName]);
			if (_request != null && _request.params != null)
				ObjectUtils.merge(_stateManager, _request.params[_session.stateManagerPropName]);
			_stateManager.state = AbstractIo3Manager.STATE_IN;
			
			// Dispatch an Init event when the targetObject is fully displayed and initalized.
			var eventType:String = isPage() ? PagesEvent.PAGE_INIT : PagesEvent.BLOCK_INIT;
			var pagesEvent:PagesEvent = new PagesEvent(eventType, false, false, null, _context, _request, _session, _target);
			dispatchEvent(pagesEvent);
			
		}
		
		protected function isPage():Boolean {
			return _context is PageContext;
		}
		
		
		// EVENT HANDLERS
		
		protected function stateChangeHandler(event:StateEvent):void {
			
			var type:String;	
			if (event.state == AbstractIo3Manager.STATE_IN) {
				type = isPage() ? PagesEvent.PAGE_IN : PagesEvent.BLOCK_IN;
			} else if (event.state == AbstractIo3Manager.STATE_ON) {
				type = isPage() ? PagesEvent.PAGE_ON : PagesEvent.BLOCK_ON;
			} else if (event.state == AbstractIo3Manager.STATE_OUT) {
				type = isPage() ? PagesEvent.PAGE_OUT : PagesEvent.BLOCK_OUT;
			} else if (event.state == AbstractIo3Manager.STATE_OFF) {
				type = isPage() ? PagesEvent.PAGE_OFF : PagesEvent.BLOCK_OFF;
				destroy();
			}
			
			var pagesEvent:PagesEvent = new PagesEvent(type, false, false, null, _context, 
													   _request, _session, _target);
			dispatchEvent(pagesEvent);
			
		}

		
		// GETTER/SETTER
		
		public function get isDisplayed():Boolean {
			return _isDisplayed;
		}
		
		public function get isCleared():Boolean {
			return _isCleared;
		}

		public function get context():AbstractPageBlockContext {
			return _context;
		}
		
		public function set context(context:AbstractPageBlockContext):void {
			
			_context = context;
			
			// Merge context properties with display and state managers.
			if (_context != null && _context.props != null)
				ObjectUtils.merge(this, _context.props[_session.masterPropName]);
			
		}
		
		public function get request():AbstractPageBlockRequest {
			return _request;
		}
		
		public function set request(request:AbstractPageBlockRequest):void {
			
			_request = request;
			
			// Merge request properties with display and state managers.
			if (_request != null && _request.params != null)
				ObjectUtils.merge(this, _request.params[_session.masterPropName]);
			
		}
		
		public function get session():PagesSession {
			return _session;
		}
		
		public function set session(session:PagesSession):void {
			_session = session;
		}
		
		public function get stateManager():StateManager {
			return _stateManager;
		}
		
		public function set stateManager(stateManager:StateManager):void {
			_stateManager = stateManager;
			_stateManager.addEventListener(StateEvent.STATE_CHANGE, stateChangeHandler, false, 0, true);
			EventUtils.propagate(_stateManager, this, StateEvent.STATE_CHANGE);
		}

		public function get container():DisplayObjectContainer {
			return _container;
		}
		
		public function get target():Object {
			return _target;
		}
		
		
	}
	
}
